home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SGI Developer Toolbox 6.1
/
SGI Developer Toolbox 6.1 - Disc 4.iso
/
public
/
radio
/
radio.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-08-01
|
21KB
|
939 lines
/***********************************************************
Copyright 1991, 1992, 1993 by Stichting Mathematisch Centrum,
Amsterdam, The Netherlands.
All Rights Reserved
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the names of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
******************************************************************/
/* Receive audio UDP packets transmitted by broadcast.
Command line options:
-p port tune to this port number (default 54321)
(port numbers 1..99 are shorthands for 54321 and up)
-v volume output volume (range 0-100; default unchanged)
(you can auso use [x_]gaintool or a similar tool to
set the output volume etc.)
-c port use this control port (default 54320)
-s 'secure' mode: don't listen to the control port
-f work as a filter: send output to stdout instead of
directly to the audio hardware
-l addr listen only for packets to <arg> ip address
-r addr listen only for packets from <arg>
-d debug packets
-n noninterruptable -- by default radio will be interruptable
by other sound outputting programs, hoping they do not
take too long. This option turns off that feature.
-t tee mode: send output to stdout as well as to audio h/w
-m mcastgrp multicast group (SGI only)
*/
#include "radio.h"
#include "patchlevel.h"
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include "adpcm.h"
#ifdef HAVE_MCAST
#include <arpa/inet.h>
#ifdef DEFMCAST
char defmcast[] = DEFMCAST;
#else
char *defmcast = 0;
#endif /* DEFMCAST */
#endif /* HAVE_MCAST */
#ifdef USE_AL
#include <audio.h>
#include "libst.h"
long savestate[] = {
AL_OUTPUT_RATE, 0,
/* The following two must be the last pairs! */
AL_LEFT_SPEAKER_GAIN, 0,
AL_RIGHT_SPEAKER_GAIN, 0,
};
ALport aport;
void cleanup_handler();
#endif /* USE_AL */
#ifdef USE_SUN
#include <stropts.h>
#include <sun/audioio.h>
#define AUDIO_IODEV "/dev/audio"
#define AUDIO_CTLDEV "/dev/audioctl"
int interruptable = 1;
int actlfd = -1;
int afd = -1;
void sigpoll_handler();
#endif /* USE_SUN */
#ifdef USE_NX
#include <sound/sound.h>
#define NUM_BUFFER 10
SNDSoundStruct *snd[NUM_BUFFER];
#endif /* USE_NX */
#ifdef USE_LOFI
#include "lofi.h"
#include "lofiMap.h"
#include "codec.h"
#include "ringbuffers.h"
#include "libst.h"
#define DEVLOFI "/dev/lofi"
#endif /* USE_LOFI */
#ifdef USE_AF
#include <AF/AFlib.h>
#include <math.h>
int device;
AFAudioConn *aud;
AC audio_context;
AFSetACAttributes attributes;
#endif /* USE_AF */
#ifdef CHECK_X_SERVER
#include <X11/Xlib.h>
Display *xdisplay = 0;
#endif /* CHECK_X_SERVER */
/* getopt() interface */
extern int optind;
extern char * optarg;
/* Globals */
int pausing = 0; /* Flag set when pausing */
int ofd = -1; /* Output file descriptor */
int volume = -1; /* -v parameter */
int pdebug = 0; /* -p parameter */
char *mcastgrp = 0; /* -m parameter */
#ifdef USE_LOFI
struct lofiPhysDevice *outdev;
#endif /* USE_LOFI */
/* Forward functions */
void open_speaker();
void close_speaker();
void checkalive();
void setmcast(); /* Forward */
main(argc, argv)
int argc;
char **argv;
{
int receiveport = RCVPORT;
int ctlport = RADIOCTLPORT;
char real_buf[BUFFERSIZE + HEADERSIZE];
char tmp_buf[BUFFERSIZE];
int encoding;
char *buf;
int s, ctls, curs;
struct sockaddr from;
int fromlen;
int c;
int filter = 0;
int tee = 0;
int nfds;
fd_set inputset;
int n;
char *localname = (char *) NULL;
char *remotename = (char *) NULL;
struct timeval timeout;
int packetcount;
struct adpcm_state state;
#ifdef USE_AL
short obuf[BUFFERSIZE];
int i;
int curdatalinear;
#endif
#ifdef USE_NX
int akt_buf;
#endif
#ifdef USE_LOFI
DSPTime ptime;
int first_time = 1, status = FALSE;
int i;
char *np, *p;
short obuf[BUFFERSIZE];
#endif
#ifdef USE_AF
extern int device;
extern AC audio_context;
extern AFSetACAttributes attributes;
#endif /* USE_AF */
/* Always change these two macros and the following switch together! */
#define OPTIONS "c:dfl:m:np:r:stv:"
#define USAGE "usage: %s [options]\n\
User options:\n\
-p port : port to listen to (default 54321; 1..99 ==> 54321..54419)\n\
-v volume : volume setting (1-100; default unchanged)\n\
Expert options:\n\
-f : filter mode (write data to stdout)\n\
-t : tee mode (write data to stdout as well as to audio device)\n\
-n : not interruptable by other sources (Sun only)\n\
-c ctlport : control port for tuner programs (default 54320)\n\
-s : secure mode: no control port (disallow tuner programs)\n\
Guru options:\n\
-l localhost : listen to packets to this host only\n\
-r remothost : receive packets from that host only\n\
-m mcastgrp : multicast group (not always supported)\n\
-d : debugging mode (writes messages to stderr)\n\
"
while ((c = getopt(argc, argv, OPTIONS)) != EOF) {
switch (c) {
case '?':
fprintf(stderr, USAGE, argv[0]);
exit(2);
case 'p':
receiveport = atoi(optarg);
if (0 < receiveport && receiveport < 100)
receiveport += RCVPORT-1;
break;
case 'c':
ctlport = atoi(optarg);
break;
case 'l':
localname = optarg;
break;
case 'r':
remotename = optarg;
break;
case 'd':
pdebug = 1;
break;
case 'm':
#ifdef HAVE_MCAST
mcastgrp = optarg;
#else
fprintf(stderr, "(-m not supported here)\n");
#endif
break;
case 'n':
#ifdef USE_SUN
interruptable = 0;
#else
fprintf(stderr, "(-n not supported here)\n");
#endif
break;
case 's':
ctlport = -1;
break;
case 'f':
filter = 1;
tee = 0;
break;
case 't':
tee = 1;
filter = 0;
break;
case 'v':
volume = atoi(optarg);
break;
}
}
/* Meaning of the 'tee' and 'filter' flags:
At most one of these can be on.
if tee is on: write stdout and "/dev/audio";
if filter is on: write stdout only;
if both are off: write "/dev/audio" only;
where "/dev/audio" stands for whatever audio hardware we have. */
if (filter || tee)
ofd = fileno(stdout);
if (!filter) {
open_speaker();
#ifdef CHECK_X_SERVER
xdisplay = XOpenDisplay((char *)NULL);
if (xdisplay == NULL) {
fprintf(stderr,
"radio: warning: no X server -- you must kill radio when you log out!\n");
}
#endif
}
if (ctlport >= 0)
ctls = opensock("control", (char *)NULL, ctlport,
(char *)NULL, 0, 0);
else
ctls = -1;
s = opensock("data", localname, receiveport, remotename, SENDPORT, 0);
#ifdef HAVE_MCAST
if (mcastgrp)
setmcast(s, mcastgrp);
else if (defmcast)
setmcast(s, defmcast);
#endif
packetcount = 0;
for (;;) {
/*
** Wait until one of the sockets becomes ready
*/
for (;;) {
nfds = (s > ctls ? s : ctls) + 1;
FD_ZERO(&inputset);
FD_SET(s, &inputset);
if (ctls >= 0)
FD_SET(ctls, &inputset);
timeout.tv_sec = 30;
timeout.tv_usec = 0;
n = select(nfds, &inputset, 0, 0, &timeout);
if (n > 0)
break;
if (n == 0) {
checkalive();
}
else if (errno != EINTR) {
perror("select");
exit(1);
}
}
if (ctls >= 0 && FD_ISSET(ctls, &inputset))
curs = ctls;
else if (FD_ISSET(s, &inputset))
curs = s;
/*
** Read, and check for control packet
*/
fromlen = sizeof(from);
buf = real_buf;
n = recvfrom(curs, buf, HEADERSIZE + BUFFERSIZE, 0,
&from, &fromlen);
if (n <= 0) {
if (n == 0)
continue; /* Ignore empty packets */
perror("read");
break;
}
if (pdebug) {
if(pdebug == 8) {
fprintf(stderr, "8 packets received\n");
pdebug = 1;
}
else
pdebug++;
}
if (n <= CTLPKTSIZE) {
/*
** It looks like a control packet. Check it.
*/
buf[n] = '\0';
if (strncmp(buf, "radio:", 6) == 0) {
if (pdebug)
fprintf(stderr, "control packet\n");
switch(buf[6]) {
case 'e': /* Echo */
buf[6] = 'E';
sendto(curs, buf, n, 0,
&from, fromlen);
break;
case 'S': /* Status from broadcast */
if (pdebug)
fprintf(stderr,
"Status %s\n", buf);
break;
case 't': /* Tune */
if (curs != ctls) {
if (pdebug)
fprintf(stderr,
"radio: illegal tune\n");
break;
}
#ifdef USE_SUN
if (!filter) {
(void) ioctl(ofd, I_FLUSH,
FLUSHW);
}
#endif /* USE_SUN */
receiveport = atoi(buf+8);
close(s);
s = opensock("new data", localname,
receiveport, remotename,
SENDPORT, 0);
#ifdef HAVE_MCAST
if (mcastgrp)
setmcast(s, mcastgrp);
else if (defmcast)
setmcast(s, defmcast);
#endif
break;
case 'i': /* Info */
sprintf(buf, "radio:I:%d:%d:%s.%d",
!pausing, receiveport,
VERSION, PATCHLEVEL);
sendto(curs, buf, strlen(buf), 0,
&from, fromlen);
break;
#ifndef USE_NX /* XXX I don't know how to close_speaker() on the NeXT */
case 'p': /* Pause */
case '0': /* Backward compatibility */
if (!filter && !pausing) {
close_speaker();
pausing = 1;
}
break;
case 'c': /* Continue */
case '1': /* Backward compatibility */
if (pausing) {
open_speaker();
pausing = 0;
}
break;
#endif /* USE_NX */
default:
if (pdebug)
fprintf(stderr,
"radio: illegal cmd '%c'\n",
buf[6]);
}
}
else if (pdebug) {
fprintf(stderr,
"radio: ill-formatted command\n");
}
}
else if (!pausing) {
encoding = PCM_64;
#ifdef USE_AL
curdatalinear = 0;
#endif /* USE_AL */
if ((buf[0]&0xff) == AUDIO_TYPE) {
encoding = buf[1]&0xff;
buf += HEADERSIZE;
n -= HEADERSIZE;
}
else {
if (pdebug)
fprintf(stderr,
"radio: non-IVS packet\n");
continue;
}
switch (encoding) {
case PCM_64:
break;
case ADPCM_32:
n = n*2;
#ifdef USE_AL
/*
** For SGI and non-filter mode, don't convert
** via ulaw but straight to linear
*/
if (!filter) {
curdatalinear = 1;
adpcm_decoder(buf, obuf, n,
(struct adpcm_state *)0);
}
else
#endif /* USE_AL */
{
adpcm_ulaw_decoder(buf, tmp_buf, n,
(struct adpcm_state *)0);
buf = tmp_buf;
}
break;
case ADPCM_32_W_STATE:
state.valprev =
((buf[0]&0xff)<<8) | (buf[1]&0xff);
state.index = buf[2]&0xff;
buf += 3;
n -= 3;
n = n*2;
#ifdef USE_AL
if (!filter) {
adpcm_decoder(buf, obuf, n, &state);
curdatalinear = 1;
}
else
#endif /* USE_AL */
{
adpcm_ulaw_decoder(buf, tmp_buf, n,
&state);
buf = tmp_buf;
}
break;
default:
if (pdebug)
fprintf(stderr,
"radio: unknown encoding %d\n",
encoding);
/* Ignore the package */
continue;
}
#ifdef USE_AL
if (!filter) {
if (!curdatalinear)
for (i = 0; i < n; i++)
obuf[i] =
st_ulaw_to_linear(buf[i]&0xff);
ALwritesamps(aport, obuf, (long)n);
}
#endif /* USE_AL */
#ifdef USE_NX
if (!filter) {
int dummy;
char *ptr;
(void) SNDGetDataPointer(snd[akt_buf], &ptr,
&dummy, &dummy);
SNDWait(akt_buf+1);
memcpy(ptr, buf, n);
snd[akt_buf] -> dataSize = n;
SNDStartPlaying(snd[akt_buf],
akt_buf+1, 5, 0, 0, 0);
akt_buf = (akt_buf + 1) % NUM_BUFFER;
}
#endif /* USE_NX */
#ifdef USE_SUN
if (!filter) {
if (write(afd, buf, n) != n) {
perror("write afd");
break;
}
}
#endif /* USE_SUN */
#ifdef USE_LOFI
if(!filter)
lofiWriteNow(outdev, buf, n, CODEC_SECONDARY);
#endif /* USE_LOFI */
#ifdef USE_AF
if(!filter)
{
static ATime next_time;
ATime now;
int dt;
if (next_time == 0)
next_time = AFGetTime(audio_context)
+ 8000;
now = AFPlaySamples(audio_context,
next_time, n, buf);
next_time += n;
dt = next_time - now;
/* If next_time is not within 4000-12000
samples in the future, adapt it.
The current packet may be lost,
but at least the next one will be
alright, and we have saved a server
round trip. */
if (dt < 4000 || dt > 12000) {
if (pdebug)
fprintf(stderr,
"Adapting (%d)\n", dt);
next_time = now+8000;
}
}
#endif /* USE_AF */
if (filter || tee) {
if (write(ofd, buf, n) != n) {
perror("write ofd");
break;
}
}
}
if (++packetcount > (30*SAMPLINGRATE / BUFFERSIZE)) {
checkalive();
packetcount = 0;
}
}
exit(0);
}
#ifdef USE_AL
void open_speaker()
{
ALconfig config;
long pvbuf[6];
/* Fetch the original state */
ALgetparams(AL_DEFAULT_DEVICE, savestate,
sizeof(savestate) / sizeof(long));
/* Set signal handlers */
signal(SIGINT, cleanup_handler);
signal(SIGTERM, cleanup_handler);
/* Set the output sampling rate */
pvbuf[0] = AL_OUTPUT_RATE;
pvbuf[1] = SAMPLINGRATE; /* XXX Assume AL_RATE_n is n */
/* Maybe also set the volume */
if (volume >= 0) {
pvbuf[2] = AL_LEFT_SPEAKER_GAIN;
pvbuf[3] = volume*255/100;
pvbuf[4] = AL_RIGHT_SPEAKER_GAIN;
pvbuf[5] = volume*255/100;
ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 6L);
}
else
ALsetparams(AL_DEFAULT_DEVICE, pvbuf, 2L);
/* Configure and open an SGI audio port */
config = ALnewconfig();
ALsetchannels(config, AL_MONO);
ALsetwidth(config, AL_SAMPLE_16);
ALsetqueuesize(config, 16000); /* 2 seconds slop */
aport = ALopenport("radio", "w", config);
if (aport == NULL) {
perror("ALopenport");
exit(1);
}
}
void close_speaker()
{
ALcloseport(aport);
aport = NULL;
}
void cleanup_handler(sig)
int sig;
{
signal(sig, SIG_DFL);
if (!pausing) {/* Don't reset anything if we're pausing */
long n = sizeof(savestate) / sizeof(long);
if (volume < 0)
n -= 4; /* Don't reset volume if we didn't set it */
ALsetparams(AL_DEFAULT_DEVICE, savestate, n);
}
kill(getpid(), sig);
}
#endif /* USE_AL */
#ifdef USE_SUN
void open_speaker()
{
audio_info_t info;
/* Write to AUDIO_IODEV */
if ((afd = open(AUDIO_IODEV, O_WRONLY)) < 0) {
perror(AUDIO_IODEV);
exit(1);
}
/* Set the volume */
if (volume >= 0) {
AUDIO_INITINFO(&info);
info.play.gain = (AUDIO_MAX_GAIN * volume) / 100;
if (ioctl(afd, AUDIO_SETINFO, &info))
perror("volume setting");
}
/* We need to open the audio control port to detect
if someone else wants to output to /dev/audio.
If this fails (e.g., in SunOS 4.0), print a message
but don't exit. */
if (interruptable) {
if ((actlfd = open(AUDIO_CTLDEV, O_RDWR)) < 0) {
perror(AUDIO_CTLDEV);
}
else if (ioctl(actlfd, I_SETSIG, S_MSG) < 0) {
perror("I_SETSIG");
}
else if (signal(SIGPOLL, sigpoll_handler) < 0) {
perror("signal(SIGPOLL)");
exit(1);
}
}
}
void close_speaker()
{
(void) ioctl(afd, I_FLUSH, FLUSHW);
close(afd);
close(actlfd);
afd = actlfd = -1;
}
void sigpoll_handler()
{
audio_info_t ap;
if (ioctl(actlfd, AUDIO_GETINFO, &ap) < 0) {
perror("AUDIO_GETINFO");
}
else if (ap.play.waiting) {
(void) ioctl(afd, I_FLUSH, FLUSHW);
close(afd);
/* The open() call blocks until we can use the device again */
if ((afd = open(AUDIO_IODEV, O_WRONLY)) < 0) {
perror(AUDIO_IODEV);
exit(1);
}
ap.play.waiting = 0;
if (ioctl(actlfd, AUDIO_SETINFO, &ap) < 0) {
perror("AUDIO_SETINFO");
}
}
}
#endif /* USE_SUN */
#ifdef USE_NX
void open_speaker()
{
int akt_buf;
int err;
/* Alloc NUM_BUFFER Sounds */
for (akt_buf = NUM_BUFFER; akt_buf > 0; akt_buf--) {
if (err = SNDAlloc(&snd[akt_buf-1], BUFFERSIZE,
SND_FORMAT_MULAW_8,
SND_RATE_CODEC, 1, 4)) {
fprintf(stderr, "init: %s\n", SNDSoundError(err));
exit(1);
}
}
akt_buf = 0;
}
void close_speaker()
{
/* XXX how to do this? */
}
#endif /* USE_NX */
#ifdef USE_LOFI
void open_speaker()
{
int status;
extern struct lofiPhysDevice *outdev;
/* Open the devlofi device */
outdev = lofiOpen(DEVLOFI);
if(outdev == NULL)
{
fprintf(stderr, "Could not open lofi %s\n", DEVLOFI);
exit(1);
}
/* Set up the lofi hardware for playing from the internal
speaker. */
status = lofiHWInit(outdev,
NULL, /* let it find it's own lod file */
0, /* Primary codec output */
CODEC_SPEAKER, /* Secondary codec output */
0, /* Primary codec input */
0, /* Secondary codec input */
FALSE);
if(volume >0)
lofiSetOutputVolume(outdev, CODEC_SPEAKER, volume / 10);
if(status == FALSE)
{
fprintf(stderr,"Could not init the hardware,is AUDIO_ROOT set?\n");
lofiClose(outdev);
exit(1);
}
}
void close_speaker()
{
extern struct lofiPhysDevice *outdev;
lofiClose(outdev);
}
#endif /* USE_LOFI */
#ifdef USE_AF
void open_speaker()
{
int outcur, outmin, outmax, steps;
extern int device;
extern AFAudioConn *aud;
extern AC audio_context;
extern AFSetACAttributes attributes;
if((aud = AFOpenAudioConn("")) == NULL)
{
fprintf(stderr, "radio: can't open connection.\n");
exit(1);
}
/* set up audio context, find sample size and sample rate */
device = FindDefaultDevice(aud);
if(device < 0)
{
fprintf(stderr, "radio: unable find a suitable device.\n");
exit(1);
}
attributes.preempt = Mix;
attributes.start_timeout = 0;
attributes.end_silence = 0;
attributes.rec_gain = 0;
audio_context = AFCreateAC(aud, device, ACPlayGain, &attributes);
outcur = AFQueryOutputGain(audio_context, &outmin, &outmax);
if(volume == 0)
AFSetOutputGain(audio_context, outmin);
else
if(volume > 0)
{
steps = outmax - outmin;
AFSetOutputGain(audio_context, outmin + (steps * volume /100));
}
else
AFSetOutputGain(audio_context, 0);
}
void close_speaker()
{
extern AFAudioConn *aud;
extern AC audio_context;
AFFreeAC(audio_context);
AFCloseAudioConn(aud);
}
#endif /* USE_LOFI */
void checkalive()
{
#ifdef CHECK_X_SERVER
if (xdisplay) {
Window focus;
int revert_to;
if (pdebug)
fprintf(stderr, "polling X server...\n");
/* Do a simple X request that needs a server round trip...
The error handler will kill us when the server is dead,
so that radio dies when the user logs out. */
XGetInputFocus(xdisplay, &focus, &revert_to);
if (pdebug)
fprintf(stderr, "X server OK\n");
}
else if (pdebug)
fprintf(stderr, "checkalive() is a no-op\n");
#endif /* CHECK_X_SERVER */
}
void setmcast(s, group)
int s;
char *group;
{
#ifdef HAVE_MCAST
struct hostent *hostentry;
struct in_addr grpaddr;
struct in_addr ifaddr;
struct ip_mreq mreq;
if (hostentry = gethostbyname(group))
{
bcopy(hostentry->h_addr, &grpaddr.s_addr, sizeof(grpaddr.s_addr));
}
else
grpaddr.s_addr = inet_addr(group);
if (!IN_MULTICAST(grpaddr.s_addr)) {
fprintf(stderr, "Bad multicast group: %s\n", group);
exit(1);
}
ifaddr.s_addr = htonl(INADDR_ANY);
mreq.imr_multiaddr = grpaddr;
mreq.imr_interface = ifaddr;
if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq, sizeof(mreq)) < 0) {
perror("setsockopt mreq");
exit(1);
}
#endif /* HAVE_MCAST */
}
#ifdef USE_AF
/* This routine searches for a suitable device to play 8kHz uLaw
encoded audio on. */
int FindDefaultDevice(aud)
AFAudioConn *aud;
{
AFDeviceDescriptor *aDev;
int i;
for(i=0; i<ANumberOfAudioDevices(aud); i++) {
aDev = AAudioDeviceDescriptor(aud, i);
if(aDev->inputsFromPhone == 0 &&
aDev->outputsToPhone == 0 &&
aDev->playSampleFreq == 8000 &&
aDev->playBufType == MU255)
return i;
}
return -1;
}
#endif /* USE_AF */